package me.itsoo.artemis.framework.repository.config;

import lombok.SneakyThrows;
import me.itsoo.artemis.framework.core.util.AnnotationUtils;
import me.itsoo.artemis.framework.core.util.CollectionUtils;
import me.itsoo.artemis.framework.core.util.ReflectUtils;
import me.itsoo.artemis.framework.ioc.lang.Matches;
import me.itsoo.artemis.framework.repository.executor.SimpleSqlExecutor;
import me.itsoo.artemis.framework.repository.interceptor.Matched;
import me.itsoo.artemis.framework.repository.interceptor.SqlInterceptor;
import me.itsoo.artemis.framework.repository.mapper.SqlMapper;
import me.itsoo.artemis.framework.repository.mapper.SqlMapperFactory;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;

/**
 * ConfigurationFactory
 *
 * @author zxy
 * @version 0.0.1
 * @since 2023/5/24
 */
public class ConfigurationFactory {

    private final Map<String, Configuration> configCache = new HashMap<>(32);

    private static final ConfigurationFactory CF = new ConfigurationFactory(ConfigurationFactory.class);

    private final Class<?> loadType;

    public ConfigurationFactory(Class<?> loadType) {
        this.loadType = loadType;
    }

    /**
     * 创建 Configuration
     *
     * @param profile String
     * @return Configuration
     */
    public static synchronized Configuration createConfiguration(String profile) {
        return CF.getConfiguration(profile);
    }

    /**
     * 获取 Configuration
     *
     * @param profile String
     * @return Configuration
     */
    public synchronized Configuration getConfiguration(String profile) {
        return configCache.computeIfAbsent(profile, k -> getConfiguration(loadType, profile));
    }

    /**
     * 获取 Configuration
     *
     * @param loadType Class
     * @param profile  String
     * @return Configuration
     */
    public synchronized Configuration getConfiguration(Class<?> loadType, String profile) {
        return configCache.computeIfAbsent(profile, k -> getConfiguration(loadType.getClassLoader(), profile));
    }

    /**
     * 获取 Configuration
     *
     * @param classLoader ClassLoader
     * @param profile     String
     * @return Configuration
     */
    public synchronized Configuration getConfiguration(ClassLoader classLoader, String profile) {
        return configCache.computeIfAbsent(profile, k -> getConfiguration0(classLoader.getResourceAsStream(profile)));
    }

    @SneakyThrows(IOException.class)
    private Configuration getConfiguration0(InputStream is) {
        try (Reader r = new InputStreamReader(is, StandardCharsets.UTF_8)) {
            final Configuration result = getConfiguration0(r);
            initSqlExecutor(result);
            initSqlInterceptors(result);
            initSqlMappers(result);
            return result;
        }
    }

    private Configuration getConfiguration0(Reader reader) {
        final Properties prop = new Properties();
        try (Reader r = reader) {
            prop.load(r);
        } catch (IOException ignore) {
            // NOP
        }

        return new Configuration(prop);
    }

    private void initSqlExecutor(Configuration config) {
        if (Objects.isNull(config.getSqlExecutor())) {
            synchronized (loadType) {
                if (Objects.isNull(config.getSqlExecutor())) {
                    final SimpleSqlExecutor sqlExecutor = new SimpleSqlExecutor();
                    sqlExecutor.setConfig(config);
                    config.setSqlExecutor(sqlExecutor);
                }
            }
        }
    }

    private void initSqlInterceptors(Configuration config) {
        if (Objects.isNull(config.getSqlInterceptors())) {
            synchronized (loadType) {
                if (Objects.isNull(config.getSqlInterceptors())) {
                    final ClassBindingRegistry cbr = ClassBindingRegistry.get(config.getScannerBasePackage());
                    final Set<Class<SqlInterceptor>> coll = cbr.getSqlInterceptorClasses();
                    final Collection<Class<SqlInterceptor>> reColl = CollectionUtils.filterOr(coll,
                            Matched.class::isAssignableFrom, t -> AnnotationUtils.hasAnnotation(t, Matches.class));
                    config.setSqlInterceptors(CollectionUtils.transformNonNull(reColl, ReflectUtils::newInstance));
                }
            }
        }
    }

    @SuppressWarnings("rawtypes")
    private void initSqlMappers(Configuration config) {
        if (Objects.isNull(config.getSqlMapperCache())) {
            synchronized (loadType) {
                if (Objects.isNull(config.getSqlMapperCache())) {
                    final ClassBindingRegistry cbr = ClassBindingRegistry.get(config.getScannerBasePackage());
                    final Set<Class<SqlMapper>> coll = cbr.getSqlMapperClasses();
                    final Map<Class<SqlMapper>, SqlMapper> sqlMapperCache = new LinkedHashMap<>(coll.size());
                    coll.forEach(t -> sqlMapperCache.put(t, SqlMapperFactory.getSqlMapper(t, config)));
                    config.setSqlMapperCache(sqlMapperCache);
                }
            }
        }
    }
}
